package base_classes;
import grid.Direction;
import grid.items.Body;

import java.awt.Point;
import java.io.*;
import java.net.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import parameters.StaticParameters;

import connexion.EnterMessage;
import connexion.IncomingMessageReader;
import connexion.IntegerMessage;
import connexion.Message;
import connexion.PerceptionMessage;
import connexion.PlacementMessage;
import connexion.StringMessage;

public class Client {
	int width;
	int height;

	// requestSocket;
	String team;
	static ObjectOutputStream out;
	ObjectInputStream in;
	static IncomingMessageReader imr;
	Set<String> agents = new HashSet<String>();
	HashMap<String, Point> position = new HashMap<String, Point>();
	
	/**
	 * spotted agent is a quick hack to design baseline agent
	 * such aggregation of information is totally PROHIBITED for
	 * the exercice 
	 */
	HashMap<Body,Point>spottedAgents=new HashMap<Body, Point>();

	Client() {
	}

	void run() throws UnknownHostException, IOException, ClassNotFoundException {
		// 1. creating a socket to connect to the server
		
		initNetwork();
		
		startProtocol();
		
	}

	private static void initNetwork() throws UnknownHostException, IOException {
		Socket requestSocket = new Socket("localhost", Server.portNumber);

		// 2. get Input and Output streams
		out = new ObjectOutputStream(requestSocket.getOutputStream());
		out.flush();
		imr=new IncomingMessageReader(
				new ObjectInputStream(requestSocket.getInputStream()));
	}

	public Object readObject() throws IOException, ClassNotFoundException {
		Object o= imr.readFirst();
		
		if(StaticParameters.CLIENT_INCOMING_MESSAGE_VERBOSITY)
			System.out.println("client<"+o);
		return o;
		
	}

	private void startProtocol() throws ClassNotFoundException, IOException {

		getNameMessage();
		width=getWidth();
		height=getHeight();
		int nbAgents=getNbAgents();
		int p=getPlacement();
		
		for(int i=0;i<nbAgents/3;i++)
			agents.add("r"+i);
		for(int i=0;i<nbAgents/3;i++)
			agents.add("p"+i);
		for(int i=0;i<nbAgents/3;i++)
			agents.add("c"+i);

		setAgentPlacement(p);

		getPlacementFinished();
		getGameStarted();
		while(true)
		{
			setMoves();
			getMoveOutcome();
			setShots();
			getShotsOutcome();
		}
	}


	private void setShots() throws IOException {
		for(String agt:agents)
			for(Body b:spottedAgents.keySet())
				if(
				(position.get(agt).distance(spottedAgents.get(b))<World.SHOOTING_DISTANCE)
				&&isEnemy(b))
				{
					sendMessage
					(
							new PlacementMessage
							(
									agt,team,
									PlacementMessage.SHOOTING_REASON,
									spottedAgents.get(b)));
					break;
				}
	}

	private boolean isEnemy(Body b) {
		return !b.getTeam().equals(team);
	}

	private void getMoveOutcome() throws IOException, ClassNotFoundException {
		sense(StringMessage.MOVE_PHASE_FINISHED);
	}
	private void sense(String senseTermination) throws IOException, ClassNotFoundException
	{
		boolean moveOutcomeFinished=false;
		spottedAgents.clear();
		while(!moveOutcomeFinished)
		{
			Object o=readObject();
			if(o instanceof StringMessage && 
					((StringMessage)o).getValue().equals(senseTermination))
				moveOutcomeFinished=true;
			else if(o instanceof PerceptionMessage)
			{
				PerceptionMessage pm=(PerceptionMessage)o;
				
				//instead of this, you should provide this info as a
				//2apl event to the concerned agent
				position.put(pm.getAgent(),
						pm.getPosition());
				spottedAgents.putAll(pm.getEntities());
			}
		}
	}
	
	private void getShotsOutcome() throws IOException, ClassNotFoundException {
		sense(StringMessage.SHOOT_PHASE_FINISHED);
	}


	private void setMoves() throws IOException {
		if(StaticParameters.CLIENT_OUTGOING_MESSAGE_VERBOSITY)
			System.out.println("preparing move selection");
		for(String name:position.keySet())
		{
			int direction;
			Point p=position.get(name);
			if(p.x==0&&p.y==0)
				direction=Direction.NORTH;
			else if(p.x>0&&p.y==0)
				direction=Direction.WEST;
			else if(p.x==width-1&&p.y>0)
				direction=Direction.SOUTH;				
			else if(p.x%2==0&&p.y<height-1)
				direction=Direction.NORTH;
			else if(p.x%2==0&&p.y==height-1)
				direction=Direction.EAST;
			else if(p.x%2==1&&p.y>1)
				direction=Direction.SOUTH;
			else if(p.x%2==1&&p.y==1)
				direction=Direction.EAST;
			else throw new Error();
			sendMessage(new IntegerMessage(IntegerMessage.DIRECTION,name,team,direction));
		}
		if(StaticParameters.CLIENT_OUTGOING_MESSAGE_VERBOSITY)
			System.out.println("move selection done");
	}

	private void getGameStarted() throws IOException, ClassNotFoundException {
		StringMessage s=(StringMessage)readObject();
		if(!s.getReason().equals(StringMessage.GAME_STARTED))
			throw new Error();
	}

	private void getPlacementFinished() throws IOException, ClassNotFoundException {
		Object o=readObject();
		
		StringMessage s=(StringMessage)o;
		if(!s.getReason().equals(StringMessage.PLACEMENT_FINISHED))
			throw new Error();
	}

	private void setAgentPlacement(int p) throws IOException {
		//I assume my benchmark agents evolve in a squared world
		//so w==h
		
		int middle=width/2;
		int deviation=0;
		boolean deviatedUp=true;
				
		for(String agt:agents)
		{
			Point pos;
			if(deviatedUp)
				pos=new Point(middle+deviation,0);
			else pos=new Point(middle-deviation,0);
			
			if(deviatedUp)
				deviation++;
			deviatedUp=!deviatedUp;
			
			if(p==Direction.NORTH)
				pos.y=height-1;
			if(p==Direction.WEST)
			{
				pos.y=pos.x;
				pos.x=0;
			}
			if(p==Direction.EAST)
			{
				pos.y=pos.x;
				pos.x=width-1;
			}
			int type;
			if(agt.startsWith("r"))
				type=Body.ROCK;
			else if(agt.startsWith("p"))
				type=Body.PAPER;
			else
				type=Body.SCISSOR;
			sendMessage(new EnterMessage
					(
							agt,
							team,
							EnterMessage.PLACEMENT,
							type,
							pos));
			position.put(agt, pos);
		}
		System.out.println("Agents placed");
	}

	public static void sendMessage(Message m) throws IOException {
		if(StaticParameters.CLIENT_OUTGOING_MESSAGE_VERBOSITY)
			System.out.println("client>"+m);
		out.writeObject(m);
	}

	private int getNbAgents() throws IOException, ClassNotFoundException {
		IntegerMessage im=(IntegerMessage) readObject();
		if(im.getReason().equals(IntegerMessage.AGENT_NUMBER))
			return im.getValue();
		else throw new Error();
	}

	private int getPlacement() throws IOException, ClassNotFoundException {
		Object o=readObject();
		IntegerMessage im=(IntegerMessage) o;
		if(im.getReason().equals(IntegerMessage.PLACEMENT_REASON))
			return im.getValue();
		else throw new Error();
	}

	private int getHeight() throws IOException, ClassNotFoundException {
		IntegerMessage im=(IntegerMessage) readObject();
		if(im.getReason().equals(IntegerMessage.HEIGHT_REASON))
			return im.getValue();
		else throw new Error();
	}

	private int getWidth() throws IOException, ClassNotFoundException {
		IntegerMessage im=(IntegerMessage) readObject();
		if(im.getReason().equals(IntegerMessage.WIDTH_REASON))
			return im.getValue();
		else throw new Error();
	}

	private void getNameMessage() throws IOException, ClassNotFoundException {
		StringMessage m=(StringMessage)readObject();
		if(m.getReason().equals(StringMessage.NAMING))throw new Error();
		this.team=m.getValue();
		if(StaticParameters.CLIENT_INCOMING_MESSAGE_VERBOSITY)
			System.out.println(team+" online");
		
	}

	public static void main(String args[]) throws UnknownHostException, IOException, ClassNotFoundException {
		Client client = new Client();
		client.run();
	}
}
